﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Core
{
    using System;
    using System.Linq;
    using System.Text;
    using Neo4Net.GraphDb;
    using Neo4Net.Kernel.Impl.Nioneo.Store;
    using NUnit.Framework;

    [TestFixture]
    public class TestLengthyArrayPacking : AbstractNeo4NetTestCase
    {
        private static readonly string someMixedChars = "abc421#Â¤Ã¥Ã¤Ã¶(/&â‚¬";
        private static readonly string someLatin1Chars = "abcdefghijklmnopqrstuvwxyz";

        private delegate long DynamicRecordCounter();

        [Test]
        public void BitPackingOfLengthyArrays()
        {
            long arrayRecordsBefore = DynamicArrayRecordsInUse();

            // Store an int array which would w/o packing require two dynamic records
            // 4*40 = 160B (assuming data size of 120B)
            int[] arrayWhichUnpackedWouldFillTwoDynamicRecords = Enumerable.Range(0, 40)
                                                                           .Select(n => n * n)
                                                                           .ToArray();
            INode node = GraphDb.CreateNode();
            String key = "the array";
            node.SetProperty(key, arrayWhichUnpackedWouldFillTwoDynamicRecords);
            NewTransaction();

            // Make sure it only requires one dynamic record
            Assert.That(DynamicArrayRecordsInUse(), Is.EqualTo(arrayRecordsBefore + 1));
            ClearCache();
            CollectionAssert.AreEqual((int[])node.GetProperty(key), arrayWhichUnpackedWouldFillTwoDynamicRecords);
        }

        // Tests for strings, although the test class name suggests otherwise

        [Test]
        public void MakeSureLongLatin1StringUsesOneBytePerChar()
        {
            string str = StringOfLength(someLatin1Chars, PropertyStore.DefaultDataBlockSize * 2 - 1);
            MakeSureRightAmountOfDynamicRecordsUsed(str, 2, DynamicStringRecordsInUse);
        }

        [Test]
        public void MakeSureLongUtf8StringUsesLessThanTwoBytesPerChar()
        {
            string str = StringOfLength(someMixedChars, PropertyStore.DefaultDataBlockSize + 10);
            MakeSureRightAmountOfDynamicRecordsUsed(str, 2, DynamicStringRecordsInUse);
        }

        [Test]
        public void MakeSureLongLatin1StringArrayUsesOneBytePerChar()
        {
            // Exactly 120 bytes: 5b header + (19+4)*5. w/o compression 5+(19*2 + 4)*5
            string[] stringArray = new String[5];
            for (int i = 0; i < stringArray.Length; i++) stringArray[i] = StringOfLength(someLatin1Chars, 19);
            MakeSureRightAmountOfDynamicRecordsUsed(stringArray, 1, DynamicArrayRecordsInUse);
        }

        [Test]
        public void MakeSureLongUtf8StringArrayUsesLessThanTwoBytePerChar()
        {
            string[] stringArray = new String[7];
            for (int i = 0; i < stringArray.Length; i++) stringArray[i] = StringOfLength(someMixedChars, 20);
            MakeSureRightAmountOfDynamicRecordsUsed(stringArray, 2, DynamicArrayRecordsInUse);
        }

        private void MakeSureRightAmountOfDynamicRecordsUsed(object value, int expectedAddedDynamicRecords, DynamicRecordCounter recordCounter)
        {
            long stringRecordsBefore = recordCounter();
            INode node = GraphDb.CreateNode();
            node.SetProperty("name", value);
            NewTransaction();
            long stringRecordsAfter = recordCounter();
            Assert.That(stringRecordsAfter, Is.EqualTo(stringRecordsBefore + expectedAddedDynamicRecords));
        }

        private String StringOfLength(String possibilities, int length)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                builder.Append(possibilities[i % possibilities.Length]);
            }
            return builder.ToString();
        }
    }
}
